`define DATA_ID 4'h1
`define INST_ID 4'h0

`define AXI_BURST_FIXED     2'd0
`define AXI_BURST_INCR      2'd1
`define AXI_BURST_WRAP      2'd2

module AXI4Bridge #(
    parameter ADDR_WIDTH = 32,
    parameter DATA_WIDTH = 64,
    parameter MAKS_WIDTH = DATA_WIDTH / 8,

    parameter LINE_WIDTH = 128,
    parameter STRB_WIDTH = LINE_WIDTH / 8
) (
    input   logic                   clock,
    input   logic                   reset,

/* ********* AXI Master Interface ********* */
    // aw
    output  logic [3:0]             axi_awid,
    output  logic [ADDR_WIDTH-1:0]  axi_awaddr,
    output  logic [7:0]             axi_awlen,
    output  logic [2:0]             axi_awsize,
    output  logic [1:0]             axi_awburst,
    output  logic                   axi_awvalid,
    input   logic                   axi_awready,
    // w
    output  logic [DATA_WIDTH-1:0]  axi_wdata,
    output  logic [MAKS_WIDTH-1:0]  axi_wstrb,
    output  logic                   axi_wlast,
    output  logic                   axi_wvalid,
    input   logic                   axi_wready,
    // b
    /* verilator lint_off UNUSEDSIGNAL */
    input   logic [3:0]             axi_bid,
    input   logic [1:0]             axi_bresp,
    input   logic                   axi_bvalid,
    output  logic                   axi_bready,
    // ar
    output  logic [3:0]             axi_arid,
    output  logic [ADDR_WIDTH-1:0]  axi_araddr,
    output  logic [7:0]             axi_arlen,
    output  logic [2:0]             axi_arsize,
    output  logic [1:0]             axi_arburst,
    output  logic                   axi_arvalid,
    input   logic                   axi_arready,
    // r
    input   logic [3:0]             axi_rid,
    input   logic [DATA_WIDTH-1:0]  axi_rdata,
    input   logic [1:0]             axi_rresp,
    input   logic                   axi_rlast,
    input   logic                   axi_rvalid,
    output  logic                   axi_rready,
/* ********* AXI Master Interface ********* */

/* *********** ICache Interface *********** */
    input   logic                   inst_rd_req,
    input   logic [ 2:0]            inst_rd_type,
    input   logic [ADDR_WIDTH-1:0]  inst_rd_addr,
    output  logic                   inst_rd_rdy,
    output  logic                   inst_ret_valid,
    output  logic                   inst_ret_last,
    output  logic [LINE_WIDTH-1:0]  inst_ret_data,
    /* verilator lint_off UNUSEDSIGNAL */
    input   logic                   inst_wr_req,
    input   logic [ 2:0]            inst_wr_type,
    input   logic [ADDR_WIDTH-1:0]  inst_wr_addr,
    input   logic [STRB_WIDTH-1:0]  inst_wr_wstrb,
    input   logic [LINE_WIDTH-1:0]  inst_wr_data,
    output  logic                   inst_wr_rdy,
    output  logic                   inst_read_fault,
    output  logic                   inst_write_fault, // 0
/* *********** ICache Interface *********** */

/* *********** DCache Interface *********** */
    input   logic                   data_rd_req,
    input   logic [ 2:0]            data_rd_type,
    input   logic [ADDR_WIDTH-1:0]  data_rd_addr,
    output  logic                   data_rd_rdy,
    output  logic                   data_ret_valid,
    output  logic                   data_ret_last,
    output  logic [LINE_WIDTH-1:0]  data_ret_data,

    input   logic                   data_wr_req,
    input   logic [ 2:0]            data_wr_type,
    input   logic [ADDR_WIDTH-1:0]  data_wr_addr,
    input   logic [STRB_WIDTH-1:0]  data_wr_wstrb,
    input   logic [LINE_WIDTH-1:0]  data_wr_data,
    output  logic                   data_wr_rdy,
    output  logic                   data_read_fault,
    output  logic                   data_write_fault
/* *********** DCache Interface *********** */

);

/* ====================== Write ====================== */
    assign      axi_awburst = `AXI_BURST_INCR;
    assign      axi_awid    = `DATA_ID;

    assign      inst_wr_rdy = 1'b1;
    assign      data_wr_rdy = (write_state == WRITE_IDLE);

    wire        write_line_valid    = (data_wr_type == 3'b100);
    wire [7:0]  write_len           = write_line_valid ? 8'h1 : 8'h0;
    wire [2:0]  write_size          = write_line_valid ? 0'b011 : data_wr_type;

    localparam WRITE_IDLE = 3'b001;
    localparam WRITE_WAIT = 3'b010;
    localparam WRITE_DONE = 3'b100;

    logic [2:0]     write_state;

    logic [63:0]    write_buffer_data;
    logic [7:0]     write_buffer_strb;

    always_ff @(posedge clock) begin
        if (reset) begin
            write_state         <= WRITE_IDLE;

            axi_awvalid         <= 0;
            axi_awlen           <= 0;
            axi_awsize          <= 0;
            axi_awaddr          <= 0;
            axi_wvalid          <= 0;
            axi_wdata           <= 0;
            axi_wstrb           <= 0;
            axi_wlast           <= 0;

            write_buffer_data   <= 0;
            write_buffer_strb   <= 0;
        end
        else begin
            case (write_state)
                WRITE_IDLE: begin
                    if (data_wr_req) begin
                        write_state     <= WRITE_WAIT;

                        axi_awvalid         <= 1'b1;
                        axi_awlen           <= write_len;
                        axi_awsize          <= write_size;
                        axi_awaddr          <= data_wr_addr;
                        axi_wvalid          <= 1'b1;
                        axi_wdata           <= data_wr_data[63:0];
                        axi_wstrb           <= data_wr_wstrb[7:0];
                        axi_bready          <= 0;

                        if (write_line_valid)   axi_wlast   <= 1'b0;
                        else                    axi_wlast   <= 1'b1;

                        write_buffer_data   <= data_wr_data[127:64];
                        write_buffer_strb   <= data_wr_wstrb[15:8];
                    end
                end
                WRITE_WAIT: begin
                    if (axi_awvalid & axi_awready) begin
                        axi_awvalid     <= 0;
                    end
                    if (axi_wvalid & axi_wready) begin
                        if (~axi_wlast) begin // Cache 第一拍
                            axi_wdata   <=  write_buffer_data;
                            axi_wstrb   <=  write_buffer_strb;
                            axi_wlast   <=  1'b1;
                        end
                        else begin // Uncache 或 Cache 最后一拍
                            write_state <= WRITE_DONE;
                            axi_wvalid  <= 0;
                            axi_wlast   <= 0; /// !!!
                            axi_bready  <= 1'b1;
                        end
                    end
                end
                WRITE_DONE: begin
                    if (axi_bready & axi_bvalid) begin
                        write_state <= WRITE_IDLE;
                        axi_wdata   <= 0;
                        axi_wstrb   <= 0;
                        axi_bready  <= 1'b0;
                    end
                end
                default: begin
                    write_state <= WRITE_IDLE;
                end
            endcase
        end
    end

    wire    wait_write = ~(write_state == WRITE_IDLE);
/* ====================== Write ====================== */


/* ====================== Read ====================== */
    assign      axi_arburst     = `AXI_BURST_INCR;

    wire read_data  = data_rd_req;
    wire read_inst  = inst_rd_req & ~read_data;
    wire read_req   = read_data | read_inst;

    wire read_req_state_is_idle = (read_req_state == READ_REQ_IDLE);
    wire no_wait_write = (wait_write & axi_bvalid & axi_bready) | (~wait_write);
    wire read_rdy   = read_req_state_is_idle & no_wait_write;
    assign data_rd_rdy = read_rdy;
    assign inst_rd_rdy = read_rdy & ~read_data;

    wire [2:0] read_type_array [1:0];
    assign read_type_array[0]   = inst_rd_type;
    assign read_type_array[1]   = data_rd_type;
    wire [31:0] read_addr_array [1:0];
    assign read_addr_array[0]   = inst_rd_addr;
    assign read_addr_array[1]   = data_rd_addr;

    wire [3:0]      read_id     = read_data ? `DATA_ID : `INST_ID;
    wire            read_line   = read_type_array[read_id[0]] == 3'b100;
    wire [31:0]     read_addr   = read_addr_array[read_id[0]];
    wire [7:0]      read_len    = read_line ? 8'h1 : 8'h0;
    wire [2:0]      read_size   = read_line ? 0'b011 : read_type_array[read_id[0]];

    localparam  READ_REQ_IDLE = 3'b001;
    localparam  READ_REQ_WAIT = 3'b010;
    
    logic [2:0]     read_req_state;

    always_ff @(posedge clock) begin
        if (reset) begin
            read_req_state  <= READ_REQ_IDLE;
            axi_arvalid     <= 0;
            axi_arid        <= 0;
            axi_arsize      <= 0;
            axi_araddr      <= 0;
        end
        else begin
            case (read_req_state)
                READ_REQ_IDLE: begin
                    if (read_req & no_wait_write) begin
                        read_req_state  <= READ_REQ_WAIT;

                        axi_arid        <= read_id;
                        axi_araddr      <= read_addr;
                        axi_arsize      <= read_size;
                        axi_arlen       <= read_len;
                        axi_arvalid     <= 1'b1;
                    end
                end
                READ_REQ_WAIT: begin
                    if (axi_arvalid & axi_arready) begin
                        read_req_state  <=  READ_REQ_IDLE;
                        axi_arvalid     <=  1'b0;
                    end
                end
                default: begin
                    read_req_state <= READ_REQ_IDLE;
                end
            endcase
        end
    end

    localparam  READ_RESP_IDLE = 3'b001;
    localparam  READ_RESP_WAIT = 3'b010;
    
    logic [2:0]     read_resp_state;

    logic [DATA_WIDTH-1:0]  read_buffer_data;
    logic                   read_line_valid;

    always_ff @(posedge clock) begin
        if (reset) begin
            read_resp_state <=  READ_RESP_IDLE;
            axi_rready      <=  1'b1;
            read_line_valid <=  1'b0;
        end
        else begin
            case (read_resp_state)
                READ_RESP_IDLE: begin
                    if (axi_rready & axi_rvalid) begin
                        if (~axi_rlast) begin // Cache 第一拍
                            read_resp_state     <= READ_RESP_WAIT;
                            read_line_valid     <=  1'b1;
                            read_buffer_data    <=  axi_rdata;
                        end // Uncache 或 Cache 最后一拍
                        else begin
                            read_resp_state     <= READ_RESP_IDLE;
                            read_line_valid     <=  1'b0;
                        end
                    end
                end
                READ_RESP_WAIT: begin
                    if (axi_rready & axi_rvalid & axi_rlast) begin
                        read_resp_state     <=  READ_RESP_IDLE;
                        read_line_valid     <=  1'b0;
                    end
                end
                default: begin
                    read_resp_state <= READ_RESP_IDLE;
                end
            endcase
        end
    end

    wire [LINE_WIDTH-1:0] ret_data = read_line_valid ? { axi_rdata, read_buffer_data } : { 64'h0, axi_rdata };
    assign  inst_ret_data = ret_data;
    assign  data_ret_data = ret_data;

    wire is_inst_id         = (axi_rid == `INST_ID);
    wire is_data_id         = (axi_rid == `DATA_ID);
    assign  inst_ret_valid  = is_inst_id & axi_rvalid;
    assign  data_ret_valid  = is_data_id & axi_rvalid;
    assign  inst_ret_last   = is_inst_id & axi_rlast;
    assign  data_ret_last   = is_data_id & axi_rlast;
    wire    rresp_err = axi_rvalid & (|axi_rresp);
    assign  inst_read_fault = is_inst_id & rresp_err;
    assign  data_read_fault = is_data_id & rresp_err;
    wire    bresp_err = axi_bvalid & (|axi_bresp);
    assign  inst_write_fault= 1'b0;
    assign  data_write_fault= is_data_id & bresp_err;
/* ====================== Read ====================== */

endmodule //AXI4Bridge
